package pl.softech.engine3d.input;

import java.awt.AWTException;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.ImageIcon;
import javax.swing.SwingUtilities;

public class InputManager implements KeyListener, MouseListener, 
	MouseMotionListener, MouseWheelListener{

	public static final Cursor INVISIBLE_CURSOR = 
		Toolkit.getDefaultToolkit().createCustomCursor(
				new ImageIcon("").getImage(), new Point(0,0), "Invisible");
	
	
	public enum MouseCode {
		MOVE_LEFT(0, "Mysz lewo"),
		MOVE_RIGHT(1, "Mysz prawo"),
		MOVE_UP(2, "Mysz góra"),
		MOVE_DOWN(3, "Mysz dół"),
		WHEEL_UP(4, "Kółko myszy góra"),
		WHEEL_DOWN(5, "Kółko myszy dół"),
		BUTTON_1(6, "Przycisk myszy 1"),
		BUTTON_2(7, "Przycisk myszy 2"),
		BUTTON_3(8, "Przycisk myszy 3");
		
		public int code;
		public String name;
		public static Map<Integer, MouseCode> keyEventBtnCodeToMousCode;
		private MouseCode(int code, String name) {
			this.code = code;
			this.name = name;
			
		}
		static {
			initMap();
		}
		
		public static void initMap() {
			if(keyEventBtnCodeToMousCode != null) return;
			keyEventBtnCodeToMousCode = new HashMap<Integer, MouseCode>();
			keyEventBtnCodeToMousCode.put(MouseEvent.BUTTON1, BUTTON_1);
			keyEventBtnCodeToMousCode.put(MouseEvent.BUTTON2, BUTTON_2);
			keyEventBtnCodeToMousCode.put(MouseEvent.BUTTON3, BUTTON_3);
		}
		
		public static MouseCode getInstance(int code) {
			for(MouseCode mc : MouseCode.values())
				if(mc.code == code)
					return mc;
			return null;
		}
		
		public static String getName(int code) {
			return getInstance(code).name;
		}
	}
	
	private static final int NUM_KEY_CODES = 600;
	
	private GameAction[] keyActions = new GameAction[NUM_KEY_CODES];
	private GameAction[] mouseActions = new GameAction[MouseCode.values().length];
	
	private Point mouseLocation;
	private Point centerLocation;
	private Component comp;
	private Robot robot;
	private boolean isRecentering;
	
	public InputManager(Component comp) {
		this.comp = comp;
		mouseLocation = new Point();
		centerLocation = new Point();
		
		//rejestracja słuchaczy
		comp.addKeyListener(this);
		comp.addMouseListener(this);
		comp.addMouseMotionListener(this);
		comp.addMouseWheelListener(this);
		
		comp.setFocusTraversalKeysEnabled(false);
		
	}
	
	public void setCursor(Cursor cursor) { comp.setCursor(cursor); }
	
	public boolean isRelativeMouseMode() { return (robot != null); }
	
	public synchronized void recenterMouse() {
		if(robot == null || !comp.isShowing())
			return;
 		centerLocation.x = comp.getWidth() / 2;
 		centerLocation.y = comp.getHeight() / 2;
 		SwingUtilities.convertPointToScreen(centerLocation, comp);
 		isRecentering = true;
		robot.mouseMove(centerLocation.x, centerLocation.y);
	}
	
	public void setRelativeMouseMode(boolean bMode) {
		if(bMode == isRelativeMouseMode())
			return;
		if(bMode) {
			try {
				robot = new Robot();
				recenterMouse();
			} catch (AWTException e) {
				e.printStackTrace();
			}
			
		} else robot = null;
	}
	
	/**
	 *  Kojarzy obiekt gameAction z danym klawiszem. Kody klawiszy są zdefiniowane
	 *  w <class>java.awt.KeyEvent</class>
	 *  @see java.awt.KeyEvent
	 */
	public void mapToKey(GameAction gameAction, int keyCode) {
		keyActions[keyCode] = gameAction;
	}
	
	/**
	 *  Kojarzy obiekt gameAction z danym zdarzeniem myszy.
	 */
	public void mapToMouse(GameAction gameAction, MouseCode mouseCode) {
		mouseActions[mouseCode.code] = gameAction;
	}
	
	public void clearMap(GameAction gameAction) {
		for(int i = 0; i < keyActions.length; i++)
			if(keyActions[i] == gameAction)
				keyActions[i] = null;
		
		for(int i = 0; i < mouseActions.length; i++)
			if(mouseActions[i] == gameAction)
				mouseActions[i] = null;
	}
	
	/**
	 * Zwraca liste nazw klawiszy/zdarzeń myszy związanym
	 * z danym obiektem GameAction.
	 * @return Lista nazw klawiszy/zdarzeń myszy związanym
	 * z danym obiektem GameAction lub liste pustą
	 */
	public List<String> getMaps(GameAction gameAction) {
		List<String> ret = new LinkedList<String>();
		for(int i = 0; i < keyActions.length; i++)
			if(keyActions[i] == gameAction)
				ret.add(KeyEvent.getKeyText(i));
		
		for(int i = 0; i < mouseActions.length; i++)
			if(mouseActions[i] == gameAction)
				ret.add(MouseCode.getName(i));
		
		return ret;
	}
	
	public void resetAllGameActions() {
		for(int i = 0; i < keyActions.length; i++)
			if(keyActions[i] != null)
				keyActions[i].reset();
		for(int i = 0; i < mouseActions.length; i++)
			if(mouseActions[i] != null)
				mouseActions[i].reset();
	}
	
	/**
	 * Zwraca współrzędną X kursora myszy
	 */
	public int getMouseX() { return mouseLocation.x; }
	
	/**
	 * Zwraca współrzędną Y kursora myszy
	 */
	public int getMouseY() { return mouseLocation.y; }
	
	public GameAction getKeyAction(KeyEvent event) {
		int keyCode = event.getKeyCode();
		if(keyCode >= keyActions.length) return null;
		return keyActions[keyCode];
	} 
	
	public GameAction getMousButtonAction(MouseEvent event) {
		MouseCode mouseCode = MouseCode.keyEventBtnCodeToMousCode.get(event.getButton());
		if(mouseCode == null) return null;
		return mouseActions[mouseCode.code];	
		
	}
	
	@Override
	public void keyPressed(KeyEvent e) {
		GameAction gameAction = getKeyAction(e);
		if(gameAction != null)
			gameAction.press();
		e.consume();
	}

	@Override
	public void keyReleased(KeyEvent e) {
		GameAction gameAction = getKeyAction(e);
		if(gameAction != null)
			gameAction.release();
		e.consume();
	}

	@Override
	public void keyTyped(KeyEvent e) {
		e.consume();
	}

	@Override
	public void mouseClicked(MouseEvent e) {}

	@Override
	public void mouseEntered(MouseEvent e) {
		mouseMoved(e);
	}

	@Override
	public void mouseExited(MouseEvent e) {
		mouseMoved(e);
	}

	@Override
	public void mousePressed(MouseEvent e) {
		GameAction gameAction = getMousButtonAction(e);
		if(gameAction != null)
			gameAction.press();
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		GameAction gameAction = getMousButtonAction(e);
		if(gameAction != null)
			gameAction.release();
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		mouseMoved(e);
	}

	@Override
	public synchronized void mouseMoved(MouseEvent e) {
		if(isRecentering && centerLocation.x == mouseLocation.x &&
		   centerLocation.y == mouseLocation.y) {
			
			isRecentering = false;
			
		} else {
			int dx = e.getX() - mouseLocation.x;
			int dy = e.getY() - mouseLocation.y;
			mouseHelper(MouseCode.MOVE_LEFT, MouseCode.MOVE_RIGHT, dx);
			mouseHelper(MouseCode.MOVE_UP, MouseCode.MOVE_DOWN, dy);
			
			if(isRelativeMouseMode())
				recenterMouse();
		}
		
		mouseLocation.x = e.getX();
		mouseLocation.y = e.getY();
		
		
	}
	
	private void mouseHelper(MouseCode negCode, MouseCode posCode, int amount) {
		
		GameAction gameAction;
		if(amount < 0)
			gameAction = mouseActions[negCode.code];
		else gameAction = mouseActions[posCode.code];
		
		if(gameAction != null) {
			gameAction.press(Math.abs(amount));
			gameAction.release();
		}
	}

	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
		mouseHelper(MouseCode.WHEEL_UP, MouseCode.WHEEL_DOWN, e.getWheelRotation());
		
	}

	
}
